#include "CNCDeps.hpp"

#include "Threading.hpp"

struct computePreds {
  // condsE must be a tuple holding <zero, false, ... false>
  template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM, typename... BOOLS>
  static void compute(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> const & tt,
                      std::tuple<BOOLS...> &condsE) {
    // static_assert(tuple_size);
    constexpr int NPARAMS = TASKTAGDIM - INTERTASKDIM;
    
    // Tuple holding <One, t1 ... t_INTERTASKDIM, p1, ... p_NPARAMS>
    auto termsE = 
      std::tuple_cat(make_tuple(ConstantTerm()), 
                     makeCompositeTuple<VariableTerm, INTERTASKDIM+NPARAMS>::make());

    //
    /////////////////////////////////////////////////////////////////////////////////    
    // Still TBD how to compress this with templates
    //
    auto &t1 = std::get<1>(termsE); // inter-tile induction var
    auto &t2 = std::get<2>(termsE); // inter-tile induction var
    auto &t3 = std::get<3>(termsE); // inter-tile induction var
    auto &pT = std::get<1+INTERTASKDIM>(termsE); // parameter
    auto &pM = std::get<2+INTERTASKDIM>(termsE); // parameter
    auto &pN = std::get<3+INTERTASKDIM>(termsE); // parameter
    auto &pP = std::get<4+INTERTASKDIM>(termsE); // parameter
    
    auto p1 = 
      FOR(t1, ceil(-pN-15,16), floor(pT-3,16));
    auto p2 = 
      FOR(t2, max(t1,-t1-1), 
          min(min(floor(-8*t1+pT-2,8),floor(8*t1+pN+7,8)),floor(pT+pN-2,16)));
    auto p3 = 
      FOR (t3, max(max(0,ceil(t1+t2-1,2)),ceil(16*t2-pN-14,16)), 
           min(min(floor(pT+pN-2,16),floor(16*t2+pN+14,16)),floor(8*t1+8*t2+pN+15,16)));

    auto lbsE = 
      std::make_tuple(ConstantTerm(), 
                      (t1 >= p1.first), (t2 >= p2.first), (t3 >= p3.first));
    auto ubsE = 
      std::make_tuple(ConstantTerm(), 
                      (t1 <= p1.second), (t2 <= p2.second), t3 <= (p3.second));
    //
    // END Still TBD how to compress this with templates
    /////////////////////////////////////////////////////////////////////////////////  
    //

    // tuple holding <1, tt[0], ... tt[INTERTASKDIM+NPARAMS-1]>
    auto tagAsTupleE = std::tuple_cat(make_tuple(1), tt.toTuple());

    computePredsHelper<TASKTAGDIM, INTERTASKDIM, 1, INTERTASKDIM>::compute
      (condsE, lbsE, ubsE, tagAsTupleE);
  }
};

template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
int64_t polyTask_tuner<TASK_ID, TASKTAGDIM, INTERTASKDIM>::compute_on
(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &, 
 polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> &) const {
  // if ((t[1] + t[2]) % 16 < 8) return 0;
  // return 1;
  Threading::dumpThreadInfo(std::cerr, " compute_on ");
  return 1;
}

template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
template<class DC>
void polyTask_tuner<TASK_ID, TASKTAGDIM, INTERTASKDIM>::depends
(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> & t, 
 polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c, DC & dc ) const {  

  Threading::dumpThreadInfo(std::cerr, " depends_on ");

  TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> tt(t);

  auto condsE = makeIdenticalTuple<bool, INTERTASKDIM+1>::make(false);
  computePreds::compute<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt, condsE);
  CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, INTERTASKDIM>::genDeps(c, dc, condsE, tt);

  return;
}



template <int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
int64_t polyTask_step<TASK_ID, TASKTAGDIM, INTERTASKDIM>::execute
(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &t, 
 polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c) const
{
  stringstream ss;
  ss << t;
  Threading::dumpThreadInfo(std::cerr, ss.str());

  // Just used for signalling dependences atm, don't care about the actual value
  // in shared memory
  void *wA = nullptr;
  TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> tt(t);
  // Regular gets
  //
  auto condsE = makeIdenticalTuple<bool, INTERTASKDIM+1>::make(false);
  computePreds::compute<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt, condsE);
  CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, INTERTASKDIM>::genGets(c, condsE, tt, wA);
  // This is really for the first task only that has no antecedents
  // 
  CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, INTERTASKDIM>::genZeroGet(c, condsE, wA);
  assert(wA != nullptr);

  {
    #include "definitions.h"
    int64_t t1 = tt[0];
    int64_t t2 = tt[1];
    int64_t t3 = tt[2];
    int64_t pT = tt[3];
    int64_t pM = tt[4];
    int64_t pN = tt[5];
    int64_t pP = tt[6];

    double (*M)[2][Npadded][Npadded][Npadded] = (double (*)[2][Npadded][Npadded][Npadded])(wA);

    int64_t t4, t5, t6, t7, t8, lbv, ubv;
    for (t4=max(max(max(0,ceild(t1+t2-124,125)),ceild(16*t2-pN-998,1000)),ceild(16*t3-pN-998,1000));t4<=min(min(min(min(floord(8*t1+pN+7,500),floord(pT+pN-2,1000)),floord(16*t2+pN+14,1000)),floord(16*t3+pN+14,1000)),floord(8*t1+8*t2+pN+15,1000));t4++) {
      for (t5=max(max(max(max(max(0,8*t1+8*t2),16*t1+1),16*t2-pN),16*t3-pN),1000*t4-pN);t5<=min(min(min(min(min(pT-2,16*t2+14),16*t3+14),1000*t4+998),8*t1+8*t2+15),16*t1+pN+15);t5++) {
        if (t5%2 == 0) {
          for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
            for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
              lbv=max(1000*t4,t5+1);
              ubv=min(1000*t4+999,t5+pN);

              Threading::dumpMemInfo(std::cerr, &M[0][0][-t5+t6][-t5+t7][-t5+lbv]);
              Threading::dumpMemInfo(std::cerr, &M[0][1][-t5+t6][-t5+t7][-t5+lbv]);

#pragma vector always
#pragma ivdep
              for (t8=lbv;t8<=ubv;t8++) {
                M[0][1][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][0][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7][-t5+t8+1])+M[0][0][-t5+t6][-t5+t7][-t5+t8];;
              }
            }
          }
        }else{
          for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
            for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
              lbv=max(1000*t4,t5+1);
              ubv=min(1000*t4+999,t5+pN);

              Threading::dumpMemInfo(std::cerr, &M[0][0][-t5+t6][-t5+t7][-t5+lbv]);
              Threading::dumpMemInfo(std::cerr, &M[0][1][-t5+t6][-t5+t7][-t5+lbv]);

#pragma ivdep
#pragma vector always
              for (t8=lbv;t8<=ubv;t8++) {
                M[0][0][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][1][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7][-t5+t8+1])+M[0][1][-t5+t6][-t5+t7][-t5+t8];;
              }
            }
          }
        }
      }
    }
    c.M.put(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>(t1,  t2,  t3,  pT,  pM,  pN,  pP), M);  
  }  
  return CnC::CNC_Success;
}


template <int TASK_ID, int NPARAMS, int TASKTAGDIM, int INTERTASKDIM>
void PolyTask<TASK_ID, NPARAMS, TASKTAGDIM, INTERTASKDIM>::operator()() {
  polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> c;

#ifdef DEBUG
  CnC::debug::collect_scheduler_statistics( c );
  CnC::debug::trace( c.m_tags );
  CnC::debug::trace( c.M );
#endif //DEBUG   

  tbb::tick_count tick = tbb::tick_count::now();

  {
    const int pT = params[0];
    const int pM = params[1];
    const int pN = params[2];
    const int pP = params[3];
    c.M.put(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>
            (toArray(makeIdenticalTuple<int64_t, TASKTAGDIM>::make(ZERO_TASK_INDEX))), M);

#include "definitions.h"
    int64_t t1, t2, t3;
    for (t1=ceild(-pN-15,16);t1<=floord(pT-3,16);t1++) {
      for (t2=max(t1,-t1-1);
           t2<=min(min(floord(-8*t1+pT-2,8),floord(8*t1+pN+7,8)),floord(pT+pN-2,16));t2++) {
        for (t3=max(max(0,ceild(t1+t2-1,2)),ceild(16*t2-pN-14,16));
             t3<=min(min(floord(pT+pN-2,16),floord(16*t2+pN+14,16)),floord(8*t1+8*t2+pN+15,16));
             t3++) {
          TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> t(t1, t2, t3,  pT,  pM,  pN, pP);
          stringstream ss;          
          ss << "Putting tag: " << t;
          Threading::dumpThreadInfo(std::cerr, ss.str());

          c.m_tags.put(t);
        }
      }
    }
    // Wait for all steps to finish
    c.wait();

    printf("Total time for init and parallel execution of %d iterations of a" \
           "polyTask of size %d x %d x %d: %g sec\n", 
           pT, pN, pN, pN, (tbb::tick_count::now()-tick).seconds());
  }
}


#include "explicitInstantiations.cpp.include"
